package cn.com.code.design.mediator;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author zhengpei
 * @date 2021/10/24
 */
public class DefaultSqlSession implements SqlSession {

  private Connection connection;

  private Map<String, XNode> mapperElement;

  public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {
    this.connection = connection;
    this.mapperElement = mapperElement;
  }

  @Override
  public <T> T selectOne(String statement) {
    try {
      XNode xNode = mapperElement.get(statement);
      PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
      ResultSet resultSet = preparedStatement.executeQuery();
      List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
      return objects.get(0);
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  @Override
  public <T> T selectOne(String statement, Object parameter) {
    XNode xNode = mapperElement.get(statement);
    Map<Integer, String> parameterMap = xNode.getParameter();
    try {

      PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
      buildParameter(preparedStatement, parameter, parameterMap);
      ResultSet resultSet = preparedStatement.executeQuery();
      List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
      return objects.get(0);
    } catch (Exception e) {
      e.printStackTrace();
    }

    return null;
  }

  private void buildParameter(PreparedStatement preparedStatement, Object parameter, Map<Integer, String> parameterMap) throws SQLException, IllegalAccessException {

    int size = parameterMap.size();

    // 单个参数
    if (parameter instanceof Long) {
      // TODO 1 ?
      for (int i = 1; i <= size; i++) {
        preparedStatement.setLong(i, Long.parseLong(parameter.toString()));
      }
    }
    if (parameter instanceof Integer) {
      // TODO 1 ?
      for (int i = 1; i <= size; i++) {
        preparedStatement.setInt(i, Integer.parseInt(parameter.toString()));
      }
    }
    if (parameter instanceof String) {
      // TODO 1 ?
      for (int i = 1; i <= size; i++) {
        preparedStatement.setString(i, parameter.toString());
      }
    }

    Map<String, Object> fieldMap = new HashMap<>();
    Field[] declaredField = parameter.getClass().getDeclaredFields();
    for (Field field : declaredField) {
      String name = field.getName();
      field.setAccessible(true);
      Object obj = field.get(parameter);
      field.setAccessible(false);
      fieldMap.put(name, obj);
    }

    for (int i = 1; i <= size; i++) {
      String parameterDefine = parameterMap.get(i);
      Object obj = fieldMap.get(parameterDefine);
      if (obj instanceof Short) {
        preparedStatement.setShort(i, Short.parseShort(obj.toString()));
        continue;
      }

      if (obj instanceof Integer) {
        preparedStatement.setInt(i, Integer.parseInt(obj.toString()));
        continue;
      }

      if (obj instanceof Long) {
        preparedStatement.setLong(i, Long.parseLong(obj.toString()));
        continue;
      }

      if (obj instanceof String) {
        preparedStatement.setString(i, obj.toString());
        continue;
      }

      if (obj instanceof java.util.Date) {
        preparedStatement.setDate(i, (java.sql.Date) obj);
      }
    }

  }

  private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {
    List<T> list = new ArrayList<>();
    try {
      ResultSetMetaData metaData = resultSet.getMetaData();
      int columnCount = metaData.getColumnCount();
      while (resultSet.next()) {
        T obj = (T) clazz.newInstance();
        for (int i = 0; i <= columnCount; i++) {
          Object value = resultSet.getObject(i);
          String columnName = metaData.getColumnName(i);
          String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
          Method method;
          if (value instanceof Timestamp) {
            method = clazz.getMethod(setMethod, Date.class);
          } else {
            method = clazz.getMethod(setMethod, value.getClass());
          }
          method.invoke(obj, value);
        }
        list.add(obj);
      }

    } catch (Exception e) {
      e.printStackTrace();
    }
    return list;

  }

  @Override
  public <T> List<T> selectList(String statement) {
    XNode xNode = mapperElement.get(statement);
    try {
      PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
      ResultSet resultSet = preparedStatement.executeQuery();
      return resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  @Override
  public <T> List<T> selectList(String statement, Object parameter) {
    XNode xNode = mapperElement.get(statement);
    Map<Integer, String> parameterMap = xNode.getParameter();
    try {
      PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
      buildParameter(preparedStatement, parameter, parameterMap);
      ResultSet resultSet = preparedStatement.executeQuery();
      return resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

  @Override
  public void close() {
    if (null == connection) {
      return;
    }
    try {
      connection.close();
    } catch (SQLException e) {
      e.printStackTrace();
    }
  }

}
